package cn.tml.innermost.music.controller;

import cn.tml.innermost.framework.entity.enums.ResultCode;
import cn.tml.innermost.framework.entity.vo.ResultMessage;
import cn.tml.innermost.framework.exception.ServiceException;
import cn.tml.innermost.framework.utils.ResultUtil;
import cn.tml.innermost.music.dos.Music;
import cn.tml.innermost.music.params.SearchParams;
import cn.tml.innermost.music.vo.PageOV;
import cn.tml.innermost.music.vo.SearchVO;
import com.alibaba.fastjson.JSON;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;


@RequestMapping("listener/music/search")
@CrossOrigin
public class SearchController {

    @Autowired
    private RestHighLevelClient client;

    private static final String MUSIC_INDEX = "mt_music";

    /***
     * @description: 搜索
     * @param content
     * @param current
     * @param size
     * @return: ResultMessage
     */
    @PostMapping(value = "")
    public ResultMessage searchMusic(@RequestBody SearchParams searchParams) throws IOException {
        int current = searchParams.getCurrent();
        int size = searchParams.getSize();
        String content = searchParams.getContent();
        String searchField = searchParams.getSearchField();
        checkSearchParam(content, current, size, searchField);

        PageOV pageVO = new PageOV();
        List<Music> musicList = new ArrayList<>();
        SearchVO searchVO = new SearchVO();

        SearchRequest searchRequest = new SearchRequest(MUSIC_INDEX);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        BoolQueryBuilder firstStageQuery = createAdjustedWeightQuery(content, searchField, 1);
        BoolQueryBuilder secondStageQuery = createAdjustedWeightQuery(content, searchField, 2);

        BoolQueryBuilder combinedQuery = QueryBuilders.boolQuery()
                .should(QueryBuilders.constantScoreQuery(firstStageQuery).boost(100.0f))
                .should(secondStageQuery);

        sourceBuilder.query(combinedQuery);

        // Add highlighting
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("name")
                .field("singerName")
                .field("albumName")
                .field("lyrics")
                .preTags("<strong>")
                .postTags("</strong>")
                .requireFieldMatch(false);

        sourceBuilder.highlighter(highlightBuilder);

        searchRequest.source(sourceBuilder);
        // 计算偏移量
        int offset = (current - 1) * size;
        // 使用偏移量设置 from 参数
        sourceBuilder.from(offset).size(size);

        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits searchHits = response.getHits();

        // Pagination
        long value = searchHits.getTotalHits().value;
        pageVO.setTotal(value);
        pageVO.setCurrent((long) current);
        pageVO.setSize((long) size);
        pageVO.setPages((value + size - 1) / size);

        SearchHit[] hits = searchHits.getHits();

        // Process the search results
        for (SearchHit hit : hits) {
            String sourceAsString = hit.getSourceAsString();
            Music music = JSON.parseObject(sourceAsString, Music.class);

            // Handle highlighting
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (highlightFields.get("name") != null) {
                music.setName(highlightFields.get("name").fragments()[0].toString());
            }
            if (highlightFields.get("singerName") != null) {
                music.setSingerName(highlightFields.get("singerName").fragments()[0].toString());
            }
            if (highlightFields.get("albumName") != null) {
                music.setAlbumName(highlightFields.get("albumName").fragments()[0].toString());
            }
            if (highlightFields.get("lyrics") != null) {
                music.setLyrics(highlightFields.get("lyrics").fragments()[0].toString());
            }
            musicList.add(music);
        }
        searchVO.setObjectList(musicList);
        searchVO.setPageOV(pageVO);
        return ResultUtil.data(searchVO);
    }

    private BoolQueryBuilder createAdjustedWeightQuery(String content, String searchField, int stage) {
        if (searchField.equals("")) searchField = "name";
        float baseBoost = stage == 1 ? 1.0f : 2.0f;
        float nameBoost = "name".equalsIgnoreCase(searchField) ? baseBoost * 10.0f : baseBoost * 5.0f;
        float singerNameBoost = "singerName".equalsIgnoreCase(searchField) ? baseBoost * 10.0f : baseBoost * 4.0f;
        float albumNameBoost = "albumName".equalsIgnoreCase(searchField) ? baseBoost * 10.0f : baseBoost * 3.0f;
        float lyricsBoost = "lyrics".equalsIgnoreCase(searchField) ? baseBoost * 10.0f : baseBoost * 2.0f;

        return QueryBuilders.boolQuery()
                .should(createFuzzyQuery(content, "name", nameBoost))
                .should(createFuzzyQuery(content, "singerName", singerNameBoost))
                .should(createFuzzyQuery(content, "albumName", albumNameBoost))
                .should(createFuzzyQuery(content, "lyrics", lyricsBoost));
    }


    // Create fuzzy query
    private QueryBuilder createFuzzyQuery(String content, String fieldName, float boost) {
        return QueryBuilders.matchQuery(fieldName, content)
                .fuzziness(Fuzziness.AUTO)
                .prefixLength(2)
                .boost(boost);
    }

    // 参数校验
    private void checkSearchParam(String content, int current, int size, String searchField) {
        if (content.equals("")) {
            throw new ServiceException(ResultCode.SEARCH_CONTENT_EMPTY);
        }
        if (current < 0 || size < 0) {
            throw new ServiceException(ResultCode.SEARCH_FAIL);
        }
        if (size > 20) {
            throw new ServiceException(ResultCode.SEARCH_SIZE_TOO_BIG);
        }
        if (!(searchField.equals("") || searchField.equals("name") || searchField.equals("singerName")
                || searchField.equals("albumName") || searchField.equals("lyrics"))) {
            throw new ServiceException(ResultCode.SEARCH_FAIL_FIELD);
        }
    }

}
